package helper;

import org.apache.commons.lang.StringUtils;
import play.Logger;
import play.Play;
import play.cache.Cache;
import product.Product;
import product.ProductBrands;
import product.ProductTypes;
import product.merchant.ProductSpecMerchant;
import product.type.ProductType;
import utils.ObjectUtil;

import java.util.List;

/**
 * 该缓存 存放时间
 * 为Product 和 Type 定制 存放方法。 同时兼容其他操作
 * 底层依然为 Cache .
 * 对 Key 进行 Product_ 和 TYPE_PRODUCT 。 同时支持其他模式
 * 支持 同类Key 一键删除。 例如想删除 CACHE_PRODUCT_ 下面所有 Product。 可执行 deleteAll(CACHE_PRODUCT_KEYS);
 *  如果需要设置储存时间  支持 设置储存时间。 但建议使用 Redis.
 * Created by buhaoba on 16/6/20.
 */
public class CacheHelper {

    public static String CACHE_PRODUCT = "ULC_PRODUCT_";
    public static String CACHE_PRODUCT_KEYS = "ULC_PRODUCT_KEYS";
    public static String CACHE_TYPE_PRODUCTS_ = "ULC_TYPE_PRODUCT_";
    public static String CACHE_TYPE_PRODUCT_KEYS = "ULC_TYPE_PRODUCT_KEYS";
    public static String CACHE_BRAND_PRODUCTS_ = "ULC_BRAND_PRODUCT_";
    public static String CACHE_BRAND_PRODUCT_KEYS = "ULC_BRAND_PRODUCT_KEYS";
    private static final String defaultExpireSeconds        = "24h"; // 默认超时时间




    /**
     * 在CACHE中增加 key为type.id 的 products
     * 注: 可在 ProductTypeController中直接调用
     * objectToCache(CACHE_TYPE_PRODUCTS_ + type.id , products);
     * checkContainAndToAddKeys(type.id , CACHE_TYPE_PRODUCT_KEYS);
     * 来实现该功能。
     * @param type
     * @param products
     */
    public static void typeProductsAddToCache(ProductType type , List<Product> products) {
        if(type != null && products != null) {
            // 保存 products 到 Cache
            objectToCache(CACHE_TYPE_PRODUCTS_ + type.id , products);
            // 检测是否需要增加到  CACHE_TYPE_PRODUCT_KEYS 如果需要 则添加到 CACHE_TYPE_PRODUCT_KEYS 中
            checkContainAndToAddKeys(type.id , CACHE_TYPE_PRODUCT_KEYS);
        }

    }



    /**
     * 添加产品 到 Cache 中
     * 将 Product 存放到 Cache 后， 会自动存放 Product.id 到 Cache_PRODUCT_KEYS 中
     * 解决 想删除所有 Product的问题
     * 注: 此方法 只能添加 Product 产品
     * 可直接调用
     * objectToCache(CACHE_PRODUCT + product.id , product);
     * checkContainAndToAddKeys(product.id , CACHE_PRODUCT_KEYS);
     * 产品新增 或修改后， 将自动更新 所属品牌 或 类别 缓存
     * @param productSpecMerchant  产品信息
     */
    public static void productAddToCache (ProductSpecMerchant productSpecMerchant) {
        if(productSpecMerchant != null) {
            // 产品加入 Cache  如果已存在 则更新
            objectToCache(CACHE_PRODUCT + productSpecMerchant.id , productSpecMerchant);
            // 监测是否包含 二级ID编号 如果不包含则加入
            checkContainAndToAddKeys(productSpecMerchant.id , CACHE_PRODUCT_KEYS);

            // 更新 所属类别相关产品信息
            List<ProductTypes> productTypes = ProductTypes.findByProduct(productSpecMerchant.productSpec.product);
            for(ProductTypes types : productTypes) {
               // delete(CACHE_TYPE_PRODUCTS_ + types.id);
                objectToCache(CACHE_TYPE_PRODUCTS_ + types.id , types.findByType());
            }

            // 更新 所属品牌相关产品信息
            List<ProductBrands> productBrandses = ProductBrands.findByProduct(productSpecMerchant.productSpec.product);
            for(ProductBrands brands : productBrandses) {
//                delete(CACHE_BRAND_PRODUCTS_ + brands.id);
                objectToCache(CACHE_BRAND_PRODUCTS_ + brands.id , brands.findByBrand());
            }
        }
    }





    /**
     * 把一个 Object 对象放到 Cache中
     * 注: 此方法 可以添加 任意 Object . 但是添加时  自己要注意 Key 值.
     * 方法内将不在对Key值进行增加前缀
     * 否则查询不到
     * @param key  可任意填写 . 希望格式CACHE_XXX_ID  例如 : CACHE_PRODUCT_1
     * @param value 任意对象
     */
    public static void objectToCache(Object key , Object value) {
        //Logger.info("objectToCache -> key : %s | value : %s", key , value);
        if(ObjectUtil.checkNotBlock(key) && ObjectUtil.checkNotBlock(value)) {
            //Logger.info("objectToCache -> CheckKeyAndValue is Not Blank ");
            objectToCache(key , value , null);
        }
    }


    /**
     * 把一个 Object 对象放到 Cache中
     * 注: 此方法 可以添加 任意 Object . 但是添加时  自己要注意 Key 值.
     * 方法内将不在对Key值进行增加前缀
     * 否则查询不到
     * @param key  可任意填写 . 希望格式CACHE_XXX_ID  例如 : CACHE_PRODUCT_1
     * @param value 任意对象
     */
    public static void objectToCache(Object key , Object value , String defaultSeconds) {
        if(ObjectUtil.checkNotBlock(key) && ObjectUtil.checkNotBlock(value)) {
            if(ObjectUtil.checkBlock(defaultSeconds)) {
                defaultSeconds = defaultExpireSeconds;
            }
            //Logger.info("objectToCache content defaultSeconds -> key : %s | value : %s , defaultSeconds : %s" , key , value , defaultSeconds);
            Cache.set(key.toString() , value , defaultSeconds);
        }
    }


    /**
     * 根据产品ID 查询Cache
     * @param productId 产品Id
     * @return  如果存在 返回 Product
     *  如果不存在 返回 null
     */
    public static Product getProduct(Long productId) {
        if(ObjectUtil.checkNotBlock(productId)) {
            return (Product) getObject(CACHE_PRODUCT + productId);
        }
        return null;
    }

    /**
     * 根据产品ID 查询Cache
     * @param productId 产品Id
     * @return  如果存在 返回 Product
     *  如果不存在 返回 null
     */
    public static Product getProductRDSToCache(final Long productId) {
        if(ObjectUtil.checkNotBlock(productId)) {
           return CacheHelper.getCache(CacheHelper.CACHE_PRODUCT + productId, new CacheCallBack<Product>() {
                @Override
                public Product loadData() {
                    return Product.findById(productId);
                }
            });
        }
        return null;
    }



    /**
     * 跟据Key 获取 Cache中 储存Value
     * @param key 期待格式 CACHE_XXX_ID 例如 : CACHE_PRODUCT_ID
     * @return
     */
    public static Object getObject(String key) {
//        Logger.info("getObject -> key : %s" , key);
        if(ObjectUtil.checkNotBlock(key)) {
//            Logger.info("getObject -> key : %s | value : %s" , key , Cache.get(key));
            return Cache.get(key);
        }
        return null;
    }

    public static <T> T getCache(String key,  CacheCallBack<T> callback) {
//        Logger.info("getCache -> key : %s  ||| value : %s" , key , Cache.get(key));
       return  getCache(key , null , defaultExpireSeconds , callback);
    }

    /**
     * 跟据Key 获取 Cache中存储 value。 如果未发现， 可以通过 callBack方法 可以在Cache中 对 key 设置Value
     * @param key 要查寻的Key 。 期待格式: CACHE_xxx_ID 例如: CACHE_PRODUCT_1
     * @param targetClass  要储存的对象类型 期待格式: Product.class
     * @param expireSeconds  储存时间  可为空。 默认为  24H
     * @param callback    获取 loadDate(). 然后跟 key形成 key、Value 关系。存到 cache中
     * @param <T>
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> T getCache(String key, Class<T> targetClass, String expireSeconds, CacheCallBack<T> callback) {
        if (StringUtils.isBlank(key)) {
            throw new IllegalArgumentException("key不能为空");
        }
//        Logger.info("load cache key:(" + key + ")");
        // 尝试使用mget得到的值.
        try {
            Object cacheObject = getObject(key);
//            Logger.info("getCache ->  cacheObject : %s" , cacheObject);
            if (cacheObject != null) {
                if (targetClass != null) {
                    try {
                        // 检测类型是否正确
                        targetClass.cast(cacheObject);
                        return (T) cacheObject;
                    } catch (ClassCastException e) {
                        Logger.warn(e, "Get Object[" + key + "] from callback Error. can't cast to " + targetClass.getName()
                                + " from " + cacheObject.getClass().getName());
                        // 防止下次出错，删除后，重新加载进缓存
                        delete(key);
                        //置空cache对象，以进行重试loadData
                        cacheObject = null;
                    }
                } else {
                    return (T) cacheObject;
                }
            }
            // the cacheObject always is null
            if (callback != null) {
                // TODO: 使用类或实例级别锁粒度太粗，在并发过1k后，容易因为别的key的缓存操作而阻塞.
                synchronized (CacheHelper.class) {
                    Object dataObject = getObject(key);
                    if (dataObject == null) {
                        Logger.debug(" 调用loadData(), key:%s", key);
                        dataObject = callback.loadData();
                    }
                    if (dataObject != null) {
                        if (targetClass != null) {
                            try {
                                // 检测类型是否正确
                                targetClass.cast(dataObject);
                                Cache.set(key, dataObject, expireSeconds);
                                return (T) dataObject;
                            } catch (ClassCastException e) {
                                Logger.warn(e, "Get Object[" + key + "] from callback Error. can't cast to "
                                        + targetClass.getName() + " from " + dataObject.getClass().getName());
                            }
                        } else {
                            try {
                                Cache.set(key, dataObject, expireSeconds);
                            } catch (ClassCastException e) {
                                Logger.warn(e, "Put Object[" + key + "] to Cache Error.");
                            }
                            return (T) dataObject;
                        }
                    }
                }
            }
        } catch (Exception e) {
            Logger.warn(e, "When get cache[key:" + key + "] found exception:" + e.getMessage());
            checkException(e.getMessage());
            delete(key);
        }
        return null;
    }


    /**
     * 跟据 产品Id 删除 Cache中Product信息
     * @param productId
     */
    public static void deleteProduct(Long productId) {
        if(ObjectUtil.checkNotBlock(productId)) {
            delete(CACHE_PRODUCT + productId);
        }
    }


    /**
     * 删除需要设置 删除的Key
     * 期待格式  CACHE_XX_KEYS  和 CACHE_XXX_XXX_KEYS
     * 注: CACHE_XX_KEYS 跟 CACHE_XX_ID 必须匹配。 比如 CACHE_PRODUCT_KEYS  存放 keys。  CACHE_PRODUCT_1  存放具体内容
     * 操作时， 如果想删除 CACHE_PRODUCT_KEYS 旗下所有 商品。 先获取 CACHE_PRODUCT_KEYS 下所有存储 ProductId 。
     * 然后把 CACHE_PRODUCT_KEYS 变成  CACHE_PRODUCT_ 。 后面追加 key。  来形成 CACHE_XXX_ID的效果。
     * 例: CACHE_PRODUCT_1
     * @param keys
     */
    public static void deleteAll(String keys) {
        // 获取 缓存中的ProductId   格式如下 1,2,11,156,3,11,2
        String object_keys = getKeys(keys);
        //Logger.info("deleteAll -> object_keys : %s" , object_keys);
        if(ObjectUtil.checkNotBlock(object_keys)) {
            //跟据 , 拆分获取数组
            String[] array_keys = object_keys.split(",");
            //Logger.info("deleteAll -> array_keys : %s" , array_keys);
            for(String key : array_keys) {
                String _key = keys.replace("KEYS" , "");
                //Logger.info("deleteAll -> key : %s" , _key+key);
                delete(_key + key);
            }
            delete(keys);
        }
    }

    /**
     * 跟据Key 从 Cache中删除
     * @param key  任意类型
     *             期待格式: Cache_xxx_id 例如 CACHE_PRODUCT_1
     * 注: 如果想删除 Product 对象。 建议使用 deleteProduct  请传递 product.id
     *     如果想删除 Other 预存对象。 建议使用 deleteOther  请传递 other.id
     */
    public static void delete(String key) {
        Logger.debug("删除Key:" + key);
        if (StringUtils.isBlank(key)) {
            throw new IllegalArgumentException("key不能为空");
        }
        try {
            Cache.delete(key);
        } catch (Exception e1) {
            Logger.warn(e1, "When delete cache[key:" + key + "] found exception.");
            checkException(e1.getMessage());
        }
    }


    /**
     * 如果异常中是网络连接问题，直接关闭整个进程. 这是预警方案，先使用这个
     * @param message
     */
    private static void checkException(String message) {
        if (StringUtils.contains(message, "imed out waiting to add net.spy.memcached.protocol.ascii")) {
//            new SMSMessage("Memcached死机了", new String[] {"15026682165", "18121290128"});
            //Logger.info("出现致命的Memcache问题，先退出进程");
            Play.fatalServerErrorOccurred();
        }
    }


    /**
     * 获取缓存的 Product 信息
     * 单线程执行
     * @return
     */
    public static String getKeys(String keys) {
        synchronized (keys) {
            Object object_keys = Cache.get(keys);
            //Logger.info("getKeys -> keys : %s |  object_keys : %s", keys, object_keys);
            if (ObjectUtil.checkNotBlock(object_keys)) {
                return object_keys.toString();
            } else {
                return "";
            }
        }
    }


    /**
     * 检查 当前 二级Key 是已存在
     * @param id
     * @param keys
     * @return
     */
    public static Boolean checkContainId(Object id , String keys) {
        if(!ObjectUtil.checkNotBlock(id)) {
            return null;
        }
        String object_keys = getKeys(keys);
        if(ObjectUtil.checkNotBlock(object_keys)) {
            String[] array_Keys = object_keys.split(",");
            for(String key : array_Keys) {
                if(ObjectUtil.checkNotBlock(key) && key.equals(id.toString())) {
                    return true;
                }
            }
        }
        return false;
    }


    /**
     * 把新 Id 追加到 原 keys -> values 后面
     * keys -> value 1,5,11
     * 追加后   1,5,11,22
     * @param id
     * @param keys
     */
    public static void checkContainAndToAddKeys (Object id , String keys) {
        //Logger.info("checkContainAndToAddKeys -> id : %s | keys : %s" , id , keys);
        if(ObjectUtil.checkNotBlock(id) && ObjectUtil.checkNotBlock(keys)) {
            //Logger.info("checkNotBlock Id And Keys is True");
            //Logger.info("checkContainId  is :%s" , checkContainId(id , keys));
            if(!checkContainId(id , keys)) {
                String object_keys = getKeys(keys);
                //Logger.info("checkContainAndToAddKeys  object_keys : %s" , object_keys);
                if(ObjectUtil.checkNotBlock(object_keys)) {
                    objectToCache(keys, object_keys + "," + id);
                } else {
                    objectToCache(keys, id);
                }
            }
        }
    }


}
